/*
 * @Author: Snow
 * @Date: 2021-10-20 09:56:27
 * @Description: 骨骼 加载蒙皮
 */

import { Matrix4 } from "../../utils/Matrix4";
import { MatrixUtil } from "../../utils/MatrixUtil";
import { Primitives } from "../../utils/Primitives";
import { TextrueUtil } from "../../utils/TextureUtil";
import { Util } from "../../utils/Util";
import { WebGLUtil } from "../../utils/WebGLUtil";
import { IProgramInfo } from "../interface/IDefine";
import { MeshRenderer } from "../node/MeshRenderer";
import { Node } from "../node/Node";
import { Skin } from "../node/Skin";
import { SkinRenderer } from "../node/SkinRenderer";
import { TRS } from "../node/TRS";
import { Render } from "../Render";
import { WebglTemplateBase } from "../template/WebglTemplateBase";

export class TextureBoneSkin extends WebglTemplateBase {

    private skinProgramInfo: IProgramInfo;

    protected override initProgram(): void {
        this.vsSource = `
        attribute vec4 a_POSITION;
        attribute vec3 a_NORMAL;
        
        uniform mat4 u_projection;
        uniform mat4 u_view;
        uniform mat4 u_world;
        
        varying vec3 v_normal;
        
        void main() {
            gl_Position = u_projection * u_view * u_world * a_POSITION;
            v_normal = mat3(u_world) * a_NORMAL;
        }
        `;

        this.fsSource = `
        precision mediump float;
 
        varying vec3 v_normal;
         
        uniform vec4 u_diffuse;
        uniform vec3 u_lightDirection;
         
        void main () {
            vec3 normal = normalize(v_normal);
            float light = dot(u_lightDirection, normal) * .5 + .5;
            gl_FragColor = vec4(u_diffuse.rgb * light, u_diffuse.a);
        }
        `;

        let skinVsSource = `
            attribute vec4 a_POSITION;
            attribute vec3 a_NORMAL;
            attribute vec4 a_WEIGHTS_0;
            attribute vec4 a_JOINTS_0;
            
            uniform mat4 u_projection;
            uniform mat4 u_view;
            uniform mat4 u_world;
            uniform sampler2D u_jointTexture;
            uniform float u_numJoints;
            
            varying vec3 v_normal;
            
            // 这些偏移假设纹理每行4个像素
            #define ROW0_U ((0.5 + 0.0) / 4.)
            #define ROW1_U ((0.5 + 1.0) / 4.)
            #define ROW2_U ((0.5 + 2.0) / 4.)
            #define ROW3_U ((0.5 + 3.0) / 4.)
         
            mat4 getBoneMatrix(float jointNdx) {
            float v = (jointNdx + 0.5) / u_numJoints;
            return mat4(
                texture2D(u_jointTexture, vec2(ROW0_U, v)),
                texture2D(u_jointTexture, vec2(ROW1_U, v)),
                texture2D(u_jointTexture, vec2(ROW2_U, v)),
                texture2D(u_jointTexture, vec2(ROW3_U, v)));
            }
         
            void main() {
                mat4 skinMatrix = getBoneMatrix(a_JOINTS_0[0]) * a_WEIGHTS_0[0] +
                                    getBoneMatrix(a_JOINTS_0[1]) * a_WEIGHTS_0[1] +
                                    getBoneMatrix(a_JOINTS_0[2]) * a_WEIGHTS_0[2] +
                                    getBoneMatrix(a_JOINTS_0[3]) * a_WEIGHTS_0[3];
                mat4 world = u_world * skinMatrix;
                gl_Position = u_projection * u_view * world * a_POSITION;
                v_normal = mat3(world) * a_NORMAL;
            }
        `;
        super.initProgram();
        this.skinProgramInfo = WebGLUtil.createProgramInfo(Render.GL, skinVsSource, this.fsSource);
    }

    protected override async initBuffers(): Promise<void> {
        //super.initBuffers();
        const gl = Render.GL;

        var ext = gl.getExtension('OES_texture_float');
        if (!ext) {
            return;  // 扩展在这个设备上不存在
        }

        let gltf = await this.loadGLTF("https://webglfundamentals.org/webgl/resources/models/killer_whale/whale.CYCLES.gltf");

        const sharedUniforms = {
            u_lightDirection: Matrix4.normalize([-1, 3, 5]),
        };

        const origMatrices = new Map();
        let animSkin = (skin, a)=> {
            for (let i = 0; i < skin.joints.length; ++i) {
                const joint = skin.joints[i];
                // if there is no matrix saved for this joint
                if (!origMatrices.has(joint)) {
                    // save a matrix for joint
                    origMatrices.set(joint, joint.source.getMatrix());
                }
                // get the original matrix
                const origMatrix = origMatrices.get(joint);
                // rotate it
                const m = Matrix4.multiply(origMatrix, Matrix4.rotateX(a));
                // decompose it back into position, rotation, scale
                // into the joint
                Matrix4.decompose(m, joint.source.position, joint.source.rotation, joint.source.scale);
            }
        }

        let drawScene = () => {
            let time = this.ts;
            time = time;
            WebGLUtil.resizeCanvasToMatchDisplaySize(gl);
            gl.enable(gl.DEPTH_TEST);
            gl.enable(gl.CULL_FACE);
            gl.clearColor(.1, .1, .1, 1);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);




            // Compute the projection matrix
            let fieldOfViewRadians = Util.degToRad(60);
            let aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
            var projectionMatrix = Matrix4.perspective(fieldOfViewRadians, aspect, 1, 2000);

            let xSpeed = this.uiParam.x;
            let mm = Matrix4.rotateY(time * 0.2);
            mm = Matrix4.multiply(mm, Matrix4.translation(0, 0, 10));

            // let cameraPosition = [mm[12], mm[13], mm[14]];
            // let target = [0, 0, -2];

            const cameraPosition = [10, 0, -5];
            const target = [0, 0, -10];

            let up = [0, 1, 0];
            let cameraMatrix = Matrix4.lookAt(cameraPosition, target, up);

            // Make a view matrix from the camera matrix.
            let viewMatrix = Matrix4.invert(cameraMatrix);

            animSkin(gltf.skins[0], Math.sin(time) * .5);

            let renderDrawables = (node) => {
                for (const drawable of node.drawables) {
                    drawable.render(node, projectionMatrix, viewMatrix, sharedUniforms);
                }
            }

            for (const scene of gltf.scenes) {
                // 更新场景中的世界矩阵。
                scene.root.updateWorldMatrix();
                // 遍历场景并渲染所有renderables
                scene.root.traverse(renderDrawables);
            }
        }
        this.draw = drawScene;
    }

    private async loadGLTF(url: string): Promise<any> {
        const gl = Render.GL;
        const gltf = await this.loadJSON(url);
        // 加载所有gltf文件相关连的文件
        //const baseURL = new URL(url, location.href);
        const baseURL = new URL(url, location.href);
        gltf.buffers = await Promise.all(gltf.buffers.map((buffer) => {
            const url = new URL(buffer.uri, baseURL.href);
            return this.loadBinary(url.href);
        }));

        const defaultMaterial = {
            uniforms: {
                u_diffuse: [.5, .8, 1, 1],
            },
        };

        // 设置网格
        gltf.meshes.forEach((mesh) => {
            mesh.primitives.forEach((primitive) => {
                const attribs = {};
                let numElements;
                for (const [attribName, index] of Object.entries(primitive.attributes)) {
                    const { accessor, buffer, stride } = this.getAccessorAndWebGLBuffer(gl, gltf, index as number);
                    numElements = accessor.count;
                    attribs[`a_${attribName}`] = {
                        buffer,
                        type: accessor.componentType,
                        numComponents: this.accessorTypeToNumComponents(accessor.type),
                        stride,
                        offset: accessor.byteOffset | 0,
                    };
                }

                const bufferInfo = {
                    attribs,
                    numElements,
                    indices: null,
                    elementType: null
                };

                if (primitive.indices !== undefined) {
                    const { accessor, buffer } = this.getAccessorAndWebGLBuffer(gl, gltf, primitive.indices);
                    bufferInfo.numElements = accessor.count;
                    bufferInfo.indices = buffer;
                    bufferInfo.elementType = accessor.componentType;
                }

                primitive.bufferInfo = bufferInfo;

                // 存储图元的材质信息
                primitive.material = gltf.materials && gltf.materials[primitive.material] || defaultMaterial;
            });
        });

        const origNodes = gltf.nodes;
        const skinNodes = [];
        gltf.nodes = gltf.nodes.map((n) => {
            const { name, skin, mesh, translation, rotation, scale } = n;
            const trs = new TRS(translation, rotation, scale);
            const node = new Node(trs, name);
            const realMesh = gltf.meshes[mesh];
            if (skin !== undefined) {
                skinNodes.push({ node, mesh: realMesh, skinNdx: skin });
            } else if (realMesh) {
                node.drawables.push(new MeshRenderer(realMesh, this.programInfo));
            }
            return node;
        });

        // 设置蒙皮
        gltf.skins = gltf.skins.map((skin) => {
            const joints = skin.joints.map(ndx => gltf.nodes[ndx]);
            const { stride, array } = this.getAccessorTypedArrayAndStride(gl, gltf, skin.inverseBindMatrices);
            return new Skin(joints, array);
        });

        // 给蒙皮节点添加SkinRenderers
        for (const { node, mesh, skinNdx } of skinNodes) {
            node.drawables.push(new SkinRenderer(mesh, gltf.skins[skinNdx], this.skinProgramInfo));
        }

        let addChildren = (nodes, node, childIndices): void => {
            childIndices.forEach((childNdx) => {
                const child = nodes[childNdx];
                child.setParent(node);
            });
        }

        // 将节点加入场景图  设置父子关系
        gltf.nodes.forEach((node, ndx) => {
            const children = origNodes[ndx].children;
            if (children) {
                addChildren(gltf.nodes, node, children);
            }
        });

        // 设置场景
        for (const scene of gltf.scenes) {
            scene.root = new Node(new TRS(), scene.name);
            addChildren(gltf.nodes, scene.root, scene.nodes);
        }

        return gltf;
    }

    private async loadFile(url: string, typeFunc) {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`could not load: ${url}`);
        }
        return await response[typeFunc]();
    }

    private async loadBinary(url: string): Promise<any> {
        return this.loadFile(url, 'arrayBuffer');
    }

    private async loadJSON(url: string): Promise<any> {
        return this.loadFile(url, 'json');
    }

    // 给定一个访问器下标返回一个访问器, WebGLBuffer和一个stride
    private getAccessorAndWebGLBuffer(gl: WebGLRenderingContext, gltf: any, accessorIndex: number) {
        const accessor = gltf.accessors[accessorIndex];
        const bufferView = gltf.bufferViews[accessor.bufferView];
        if (!bufferView.webglBuffer) {
            const buffer = gl.createBuffer();
            const target = bufferView.target || gl.ARRAY_BUFFER;
            const arrayBuffer = gltf.buffers[bufferView.buffer];
            const data = new Uint8Array(arrayBuffer, bufferView.byteOffset, bufferView.byteLength);
            gl.bindBuffer(target, buffer);
            gl.bufferData(target, data, gl.STATIC_DRAW);
            bufferView.webglBuffer = buffer;
        }
        return {
            accessor,
            buffer: bufferView.webglBuffer,
            stride: bufferView.stride || 0,
        };
    }

    private throwNoKey(key) {
        throw new Error(`no key: ${key}`);
    }



    private accessorTypeToNumComponents(type) {
        const accessorTypeToNumComponentsMap = {
            'SCALAR': 1,
            'VEC2': 2,
            'VEC3': 3,
            'VEC4': 4,
            'MAT2': 4,
            'MAT3': 9,
            'MAT4': 16,
        };
        return accessorTypeToNumComponentsMap[type] || this.throwNoKey(type);
    }

    // 给定一个GL类型返回需要的类型
    private glTypeToTypedArray(type) {
        const glTypeToTypedArrayMap = {
            '5120': Int8Array,    // gl.BYTE
            '5121': Uint8Array,   // gl.UNSIGNED_BYTE
            '5122': Int16Array,   // gl.SHORT
            '5123': Uint16Array,  // gl.UNSIGNED_SHORT
            '5124': Int32Array,   // gl.INT
            '5125': Uint32Array,  // gl.UNSIGNED_INT
            '5126': Float32Array, // gl.FLOAT
        }
        return glTypeToTypedArrayMap[type] || this.throwNoKey(type);
    }

    // 给定一个访问器下标返回访问器
    // 和缓冲正确部分的类型化数组
    private getAccessorTypedArrayAndStride(gl, gltf, accessorIndex) {
        const accessor = gltf.accessors[accessorIndex];
        const bufferView = gltf.bufferViews[accessor.bufferView];
        const TypedArray = this.glTypeToTypedArray(accessor.componentType);
        const buffer = gltf.buffers[bufferView.buffer];
        return {
            accessor,
            array: new TypedArray(
                buffer,
                bufferView.byteOffset + (accessor.byteOffset || 0),
                accessor.count * this.accessorTypeToNumComponents(accessor.type)),
            stride: bufferView.byteStride || 0,
        };
    }


}